/*
    This file is part of ServerProxy.
    SocketProxy is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    Subsonic is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
    Copyright 2014 (C) Scott Jackson
*/

package net.nullsum.audinaut.util;

import android.util.Log;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;

public class FileProxy extends ServerProxy {
    private static final String TAG = FileProxy.class.getSimpleName();

    protected ProxyTask getTask(Socket client) {
        return new StreamFileTask(client);
    }

    protected class StreamFileTask extends ProxyTask {
        File file;

        public StreamFileTask(Socket client) {
            super(client);
        }

        @Override
        public boolean processRequest() {
            if (!super.processRequest()) {
                return false;
            }

            Log.i(TAG, "Processing request for file " + path);
            file = getFile(path);
            if (!file.exists()) {
                Log.e(TAG, "File " + path + " does not exist");
                return false;
            }

            // Make sure to not try to read past where the file is downloaded
            return !(cbSkip != 0 && cbSkip >= file.length());
        }

        File getFile(String path) {
            return new File(path);
        }

        Long getContentLength() {
            return file.length();
        }

        long getFileSize() {
            return file.length();
        }

        @Override
        public void run() {
            Long contentLength = getContentLength();

            // Create HTTP header
            String headers;
            if (cbSkip == 0) {
                headers = "HTTP/1.0 200 OK\r\n";
            } else {
                headers = "HTTP/1.0 206 OK\r\n";
                headers += "Content-Range: bytes " + cbSkip + "-" + (file.length() - 1) + "/";
                if (contentLength == null) {
                    headers += "*";
                } else {
                    headers += contentLength;
                }
                headers += "\r\n";

                Log.i(TAG, "Streaming starts from: " + cbSkip);
            }

            String name = file.getPath();
            int index = name.lastIndexOf('.');
            String ext = "";
            if (index != -1) {
                ext = name.substring(index + 1).toLowerCase();
            }
            if ("mp3".equals(ext)) {
                headers += "Content-Type: audio/mpeg\r\n";
            } else {
                headers += "Content-Type: " + "application/octet-stream" + "\r\n";
            }

            long fileSize;
            if (contentLength == null) {
                fileSize = getFileSize();
            } else {
                fileSize = contentLength;
                if (cbSkip > 0) {
                    headers += "Content-Length: " + (fileSize - cbSkip) + "\r\n";
                } else {
                    headers += "Content-Length: " + fileSize + "\r\n";
                }
                headers += "Accept-Ranges: bytes \r\n";
            }
            Log.i(TAG, "Streaming fileSize: " + fileSize);

            headers += "Connection: close\r\n";
            headers += "\r\n";

            long cbToSend = fileSize - cbSkip;
            OutputStream output = null;
            byte[] buff = new byte[64 * 1024];
            try {
                output = new BufferedOutputStream(client.getOutputStream(), 32 * 1024);
                output.write(headers.getBytes());

                // Make sure to have file lock
                onStart();

                // Loop as long as there's stuff to send
                while (isRunning && !client.isClosed()) {
                    onResume();

                    // See if there's more to send
                    int cbSentThisBatch = 0;
                    if (file.exists()) {
                        FileInputStream input = new FileInputStream(file);
                        input.skip(cbSkip);
                        int cbToSendThisBatch = input.available();
                        while (cbToSendThisBatch > 0) {
                            int cbToRead = Math.min(cbToSendThisBatch, buff.length);
                            int cbRead = input.read(buff, 0, cbToRead);
                            if (cbRead == -1) {
                                break;
                            }
                            cbToSendThisBatch -= cbRead;
                            cbToSend -= cbRead;
                            output.write(buff, 0, cbRead);
                            output.flush();
                            cbSkip += cbRead;
                            cbSentThisBatch += cbRead;
                        }
                        input.close();
                    }

                    // Done regardless of whether or not it thinks it is
                    if (isWorkDone()) {
                        break;
                    }

                    // If we did nothing this batch, block for a second
                    if (cbSentThisBatch == 0) {
                        Log.d(TAG, "Blocking until more data appears (" + cbToSend + ")");
                        Thread.sleep(1000);
                    }
                }

                // Release file lock, use of stream proxy means nothing else is using it
                onStop();
            } catch (SocketException socketException) {
                Log.e(TAG, "SocketException() thrown, proxy client has probably closed. This can exit harmlessly");

                // Release file lock, use of stream proxy means nothing else is using it
                onStop();
            } catch (Exception e) {
                Log.e(TAG, "Exception thrown from streaming task:");
                Log.e(TAG, e.getClass().getName() + " : " + e.getLocalizedMessage());
            }

            // Cleanup
            try {
                if (output != null) {
                    output.close();
                }
                client.close();
            } catch (IOException e) {
                Log.e(TAG, "IOException while cleaning up streaming task:");
                Log.e(TAG, e.getClass().getName() + " : " + e.getLocalizedMessage());
            }
        }

        public void onStart() {

        }

        public void onStop() {

        }

        public void onResume() {

        }

        public boolean isWorkDone() {
            return cbSkip >= file.length();
        }
    }
}
